// CallStackDumper.cpp: implementation of the CCallStackDumper class.
//
//	author:		zlong
//	(c)2006 Unispace, Inc. All Rights Reserved
//
//////////////////////////////////////////////////////////////////////

#include "CallStackDumper.h"
#include "BaseCode.h"
#include <Dbghelp.h>
#include <tchar.h>


///////////////////////////////////////////////////////////////////////////////
log_InlineLog("log/Stack", Stack)

#define LOGSTACK		log_InlineLogStack


///////////////////////////////////////////////////////////////////////////////
#define APPEND_SEARCHPATH(dest,iBufSize,src) {\
		if(iBufSize>0) \
		{ \
			STRNCAT(dest,src,iBufSize); \
			iBufSize-=STRLEN(src); \
		} \
			if(iBufSize>0) \
		{ \
			STRNCAT(dest,";",iBufSize);\
			--iBufSize;\
		}\
	}\

///////////////////////////////////////////////////////////////////////////////
typedef char * (__cdecl	*PFN_strncat)(
				char *strDest, const char *strSource, size_t count
				);

#define  STRNCAT ((PFN_strncat)::GetProcAddress(::GetModuleHandle("NTDLL.DLL"),"strncat"))

///////////////////////////////////////////////////////////////////////////////
typedef char * (__cdecl	*PFN_strrchr)(
									  const char *string, 
									  int c
									  );

#define  STRRCHR ((PFN_strrchr)::GetProcAddress(::GetModuleHandle("NTDLL.DLL"),"strrchr"))


///////////////////////////////////////////////////////////////////////////////
#define  STRLEN lstrlenA


///////////////////////////////////////////////////////////////////////////////
//
//Define Macro: MEMORY_ALLOC
//
///////////////////////////////////////////////////////////////////////////////

#define MEMORY_ALLOC(dwSize) ::HeapAlloc( \
	GetProcessHeap(), \
	HEAP_ZERO_MEMORY, \
	dwSize \
) 

///////////////////////////////////////////////////////////////////////////////
//
//Define Macro: MEMORY_FREE
//
///////////////////////////////////////////////////////////////////////////////

#define MEMORY_FREE(pvBuf) ::HeapFree(GetProcessHeap(),0,pvBuf)



bool DumpStack()
{
	HANDLE hProcess=::GetCurrentProcess();
	DWORD dwThreadId=::GetCurrentThreadId();
	HANDLE hThread=::GetCurrentThread();

	char szTemp[1024]={0,};
	unsigned int uiTempBufSize=sizeof(szTemp)-1;
	
	// Setup search path for pdb (symbol information) files:
	char szSymbolSearchPath[64*1024]={0,};
	int  iSymbolSearchPatBufSize=sizeof(szSymbolSearchPath)-1;
	int  iBufSpaceLeft=iSymbolSearchPatBufSize;
	
	if ( ::GetCurrentDirectoryA( uiTempBufSize, szTemp ) )
	{
		APPEND_SEARCHPATH(szSymbolSearchPath,iBufSpaceLeft,szTemp);
	}
	
	// dir with executable
	if ( ::GetModuleFileNameA( NULL, szTemp, uiTempBufSize ) )
	{
		char *pchToken=STRRCHR(szTemp,'\\');
		if(pchToken)
		{
			*pchToken='\0';
		}
		
		APPEND_SEARCHPATH(szSymbolSearchPath,iBufSpaceLeft,szTemp);
	}
	// environment variable _NT_SYMBOL_PATH
	if ( ::GetEnvironmentVariableA( "_NT_SYMBOL_PATH", szTemp, uiTempBufSize ) )
	{
		APPEND_SEARCHPATH(szSymbolSearchPath,iBufSpaceLeft,szTemp);
	}
	// environment variable _NT_ALTERNATE_SYMBOL_PATH
	if ( ::GetEnvironmentVariableA( "_NT_ALTERNATE_SYMBOL_PATH", szTemp, uiTempBufSize ) )
	{
		APPEND_SEARCHPATH(szSymbolSearchPath,iBufSpaceLeft,szTemp);
	}
	// environment variable SYSTEMROOT
	if ( ::GetEnvironmentVariableA( "SYSTEMROOT", szTemp, uiTempBufSize ) )
	{
		APPEND_SEARCHPATH(szSymbolSearchPath,iBufSpaceLeft,szTemp);
	}
	
	if(iBufSpaceLeft>=0) // if we added anything, we have a trailing semicolon
	{
		szSymbolSearchPath[iSymbolSearchPatBufSize-iBufSpaceLeft]='\0';
	}

	SymInitialize(hProcess, szSymbolSearchPath, TRUE);
	
	SymSetOptions( (SymGetOptions()| SYMOPT_LOAD_LINES) &~SYMOPT_UNDNAME);
	
	const int MAXNAMELEN	=1024 ;// max name length for found symbols
	const int IMGSYMLEN		= sizeof (IMAGEHLP_SYMBOL);

	char undName[MAXNAMELEN]={0,}; // undecorated name
	char undFullName[MAXNAMELEN]={0,}; // undecorated name with all shenanigans

	IMAGEHLP_MODULE Module={0,};
	IMAGEHLP_LINE Line={0,};

	DWORD dwOffsetFromSymbol= 0; // tells us how far from the symbol we were
	STACKFRAME sfStackFrame={0,}; // in/out sfStackFrame

	unsigned int	curEIP;
	unsigned int	curEBP;
	__asm {
		call ll;
	ll:
		pop eax;
		mov curEIP, eax;
		mov curEBP, ebp;
	};

	sfStackFrame.AddrPC.Offset = curEIP;
	sfStackFrame.AddrPC.Mode = AddrModeFlat;
	sfStackFrame.AddrFrame.Offset = curEBP;
	sfStackFrame.AddrFrame.Mode = AddrModeFlat;

	// normally, call ImageNtHeader() and use machine info from PE header
	DWORD dwImageType = IMAGE_FILE_MACHINE_I386;

	
	// Begin stack trace
	LOGSTACK("=========== Begin stack trace ============");
	LOGSTACK(" Nr Flags PC       Return   Frame    Stack");
	
	IMAGEHLP_SYMBOL *pSym = (IMAGEHLP_SYMBOL *) MEMORY_ALLOC(IMGSYMLEN + MAXNAMELEN);
	
	if(NULL==pSym)
	{
		LOGSTACK("!!!!!!!!!!!NULL==pSym!!!!!!!!!!!!!");
	}
	else
	{
		ZeroMemory(pSym,IMGSYMLEN + MAXNAMELEN);

		pSym->SizeOfStruct = IMGSYMLEN;
		pSym->MaxNameLength = MAXNAMELEN;

		int iFrameNum = 0;
		while (true)
		{
			// if this returns ERROR_INVALID_ADDRESS (487) or ERROR_NOACCESS (998), you can
			// assume that either you are done, or that the stack is so hosed that the next
			// deeper frame could not be found.
			
			if (!StackWalk(
				dwImageType,
				hProcess,
				hThread,
				&sfStackFrame,
				0,
				0,
				SymFunctionTableAccess,
				SymGetModuleBase,
				0 ) )
			{
				break;
			}
			
			LOGSTACK(
				"%3d %c%c    %08lx %08lx %08lx %08lx ",
				iFrameNum,
				sfStackFrame.Far? 'F': '.',
				sfStackFrame.Virtual? 'V': '.',
				sfStackFrame.AddrPC.Offset,
				sfStackFrame.AddrReturn.Offset,
				sfStackFrame.AddrFrame.Offset,
				sfStackFrame.AddrStack.Offset );
			
			
			if ( sfStackFrame.AddrPC.Offset == 0 )
			{
				LOGSTACK("(-nosymbols- PC == 0)" );
			}
			else
			{
				// we seem to have a valid PC
				
				// show procedure info (SymGetSymFromAddr())
				if ( ! SymGetSymFromAddr( hProcess, sfStackFrame.AddrPC.Offset, &dwOffsetFromSymbol, pSym ) )
				{
					if ( GetLastError() != ERROR_INVALID_ADDRESS )
					{
						LOGSTACK("SymGetSymFromAddr(): ErrorCode = %lu", GetLastError() );
					}
				}
				else
				{
					// UnDecorateSymbolName()
					UnDecorateSymbolName( pSym->Name, undName, MAXNAMELEN, UNDNAME_NAME_ONLY );
					UnDecorateSymbolName( pSym->Name, undFullName, MAXNAMELEN, UNDNAME_COMPLETE );

					LOGSTACK(undName );

					if ( dwOffsetFromSymbol != 0 )
					{
						LOGSTACK(" %+ld bytes", (long) dwOffsetFromSymbol );
					}
					
					LOGSTACK("    Sig:  %s", pSym->Name );
					
					LOGSTACK("    Decl: %s", undFullName );
				}
				
				// show line number info, NT5.0-method (SymGetLineFromAddr())
				if ( SymGetLineFromAddr != 0 )
				{ // yes, we have SymGetLineFromAddr()
					if ( ! SymGetLineFromAddr( hProcess, sfStackFrame.AddrPC.Offset, &dwOffsetFromSymbol, &Line ) )
					{
						if ( GetLastError() != ERROR_INVALID_ADDRESS )
						{
							LOGSTACK("SymGetLineFromAddr(): ErrorCode = %lu", GetLastError() );
						}
					}
					else
					{
						LOGSTACK("    Line: %s(%lu) %+ld bytes",
							Line.FileName, Line.LineNumber, dwOffsetFromSymbol );
					}
				}
				
				// show module info (SymGetModuleInfo())
				if ( ! SymGetModuleInfo( hProcess, sfStackFrame.AddrPC.Offset, &Module ) )
				{
					LOGSTACK("SymGetModuleInfo): gle = %lu", GetLastError() );
				}
				else
				{ // got module info OK
					
					LOGSTACK("    Mod:  %s[%s], base: %08lxh",
						Module.ModuleName, Module.ImageName, Module.BaseOfImage );

					switch ( Module.SymType )
					{
					case SymNone:
						LOGSTACK("    Sym:  type: %s, file: %s","-nosymbols-", Module.LoadedImageName );
						break;
					case SymCoff:
						LOGSTACK("    Sym:  type: %s, file: %s","COFF", Module.LoadedImageName );
						break;
					case SymCv:
						LOGSTACK("    Sym:  type: %s, file: %s","CV", Module.LoadedImageName );
						break;
					case SymPdb:
						LOGSTACK("    Sym:  type: %s, file: %s","PDB", Module.LoadedImageName );
						break;
					case SymExport:
						LOGSTACK("    Sym:  type: %s, file: %s","-exported-", Module.LoadedImageName );
						break;
					case SymDeferred:
						LOGSTACK("    Sym:  type: %s, file: %s","-deferred-", Module.LoadedImageName );
						break;
					case SymSym:
						LOGSTACK("    Sym:  type: %s, file: %s","SYM", Module.LoadedImageName );
						break;
					default:
						LOGSTACK("    Sym:  type: unknown(%ld) , file: %s ",(long) Module.SymType, Module.LoadedImageName );
						break;
					}
				}
			}
			
			// no return address means no deeper sfStackFrame
			if ( sfStackFrame.AddrReturn.Offset == 0 ) 
			{
				break;
			}
			
			iFrameNum++;
		}

		MEMORY_FREE(pSym);

		pSym=NULL;
	}
	
	LOGSTACK("-- End stack trace --\r\n\r\n");

	return true;
}